﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;

namespace ImageProcessingLib
{
    /// <summary>
    /// Класс обрадотки изображений.
    /// </summary>
    public class ImageProcessing
    {
        readonly int _processCount = 2;

        #region === private methods ===

        private static void Apply(int[] destinationImArray, int startIndex, int endIndex, AbstractImageEffect aif)
        {
            aif.ApplyEffect(destinationImArray, startIndex, endIndex);
        }

        /// <summary>
        /// Распараллелеливание применения эффекта.
        /// </summary>
        /// <param name="bitmapInfo">Объект BitmapInfo4.</param>
        /// <param name="aies">Класс эффекта, наследник AbstractImageEffect.</param>
        /// <returns></returns>
        private BitmapInfo4 ProcessInParallel(BitmapInfo4 bitmapInfo, IEnumerable<AbstractImageEffect> aies)
        {            
            int[] imageArray = new int[Math.Abs(bitmapInfo.Stride) * bitmapInfo.Height / 4];
            int pie = imageArray.Length / _processCount;
            int index = 0;
            Action[] actions = new Action[_processCount];

            foreach (AbstractImageEffect ai in aies)
            {
                int startIndex = index * pie;
                int endIndex = (index == aies.Count() - 1) ? imageArray.Length - 1 : (index + 1) * pie - 1;

                actions[index] = new Action(delegate() { Apply(imageArray, startIndex, endIndex, ai); });
                index++;
            }

            Parallel.Invoke(actions);

            return new BitmapInfo4
            {
                Width = bitmapInfo.Width,
                Height = bitmapInfo.Height,
                Stride = bitmapInfo.Stride,
                ImageArray4 = imageArray
            };
        }

        #endregion

        /// <summary>
        /// Яркость.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="rShift">Смещение красного канала.</param>
        /// <param name="gShift">Смещение зеленого канала.</param>
        /// <param name="bShift">Смещение синего канала.</param>
        /// <returns>Объект BitmapInfo.4</returns>
        public BitmapInfo4 Brightness(BitmapInfo4 bi, int rShift, int gShift, int bShift)
        {
            AbstractImageEffect[] aies = new Brightness[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Brightness(bi, rShift, gShift, bShift);
            }                     

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Оттенки серого.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Desaturate(BitmapInfo4 bi)
        {
            AbstractImageEffect[] aies = new Desaturate[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Desaturate(bi);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Насыщенность.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="t">Параметр насыщения. 0 - оттенки серого. 1 - нет эффекта. Больше чем 1 - увеличение насыщенности.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Saturate(BitmapInfo4 bi, float t)
        {
            AbstractImageEffect[] aies = new Saturate[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Saturate(bi, t);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Контрастность.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="t">Значение контрастности. 0 - серое изображение, 1 - нет эффекта, Больше чем 1 - увеличение контрастности.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Contrast(BitmapInfo4 bi, float t)
        {
            AbstractImageEffect[] aies = new Contrast[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Contrast(bi, t);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Размытие.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="size">Величина размытия.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Blur(BitmapInfo4 bi, int size)
        {
            AbstractImageEffect[] aies = new ImageFilter[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new ImageFilter(bi, MatrixFactory.Blur(size));
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Диффузное размытие.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="size">Величина размытия.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Diffuse(BitmapInfo4 bi, int size)
        {
            AbstractImageEffect[] aies = new Diffuse[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Diffuse(bi, size);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Инвертирование изображения.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Invert(BitmapInfo4 bi)
        {
            AbstractImageEffect[] aies = new Invert[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Invert(bi);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Оттенки цвета.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="color">Цвет, в оттенках которорого будет получено новое изображение.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Hue(BitmapInfo4 bi, int r, int g, int b)
        {
            AbstractImageEffect[] aies = new Hue[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Hue(bi, r, g, b);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Сепия эффект.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Sepia(BitmapInfo4 bi)
        {
            AbstractImageEffect[] aies = new Hue[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Hue(bi, 112, 66, 20);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Выделение контуров изображения.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Contour(BitmapInfo4 bi)
        {
            AbstractImageEffect[] aies = new ImageFilter[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new ImageFilter(bi, MatrixFactory.Contour());
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Придание рельефности изображению.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="angle">Угол подсветки в радианах.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Relief(BitmapInfo4 bi, double angle)
        {
            AbstractImageEffect[] aies = new ImageFilter[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new ImageFilter(bi, MatrixFactory.Relief(angle));
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Гамма-коррекция.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="gamma">Величина гамма коррекции.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Gamma(BitmapInfo4 bi, float gamma)
        {
            AbstractImageEffect[] aies = new Gamma[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Gamma(bi, gamma);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Гравюра.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="angle">Угол подсветки в радианах.</param>
        /// <param name="shift">Величина смещения инвертированного изображения.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Emboss(BitmapInfo4 bi, double angle, int shift)
        {
            AbstractImageEffect[] aies = new Emboss[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new Emboss(bi, angle, shift);
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Четкость деталей.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Sharpen(BitmapInfo4 bi)
        {
            AbstractImageEffect[] aies = new ImageFilter[_processCount];
            for (int i = 0; i < _processCount; i++)
            {
                aies[i] = new ImageFilter(bi, MatrixFactory.Sharpen());
            }

            return ProcessInParallel(bi, aies);
        }

        /// <summary>
        /// Кластеризация изображения.
        /// </summary>
        /// <param name="bi">Объект BitmapInfo4.</param>
        /// <param name="colorsCount">Число цветов в новом изображении.</param>
        /// <returns>Объект BitmapInfo4.</returns>
        public BitmapInfo4 Levels(BitmapInfo4 bi, int colorsCount)
        {
            // декларация алгоритма кластеризации
            IClustering kmeans = new KMeans
            {
                Metric = new Euclidean(),
                MaxIterationCount = 40
            };

            // подготовка данных для кластеризации
            var observations = bi.ImageArray4.Select(color => 
            {
                Utils.Unpack(color, out int _, out int r, out int g, out int b);
                return new Vector { X = new float[] { r, g, b } };
            }).ToArray();

            // кластеризация цветов
            kmeans.Fit(observations, colorsCount);

            // получение центров кластеров
            Vector[] centroids = kmeans.Centroids;

            // создание нового кластеризованного по цветам изображения
            var ImNewArray = bi.ImageArray4.AsParallel().Select(color =>
            {
                Utils.Unpack(color, out int _, out int r, out int g, out int b);
                Vector v = new Vector { X = new float[] { r, g, b } };
                int index = kmeans.Predict(v);
                int rc = Convert.ToInt32(centroids[index].X[0]);
                int gc = Convert.ToInt32(centroids[index].X[1]);
                int bc = Convert.ToInt32(centroids[index].X[2]);
                return Utils.CutEdges(0xFF, rc, gc, bc);
            }).ToArray();

            return new BitmapInfo4
            {
                Width = bi.Width,
                Height = bi.Height,
                Stride = bi.Stride,
                ImageArray4 = ImNewArray
            };
        }
    }
}
